#pragma once
#include <zCoreConfig.hpp>
#include <vector>
#include "Log.hpp"
#include "IOObject.hpp"

// This class wrapped std::vector
// It implements all vector's interface, so simply replace std::vector to zzz::STLVector will make it.
// It will print out error instead of crush when allocation fails.
namespace zzz {
#ifdef ZZZ_STLVECTOR
template<typename T>
class STLVector : public std::vector<T> {
public:
  typedef std::vector<T>::iterator iterator;
  typedef std::vector<T>::reverse_iterator reverse_iterator;
  typedef std::vector<T>::const_iterator const_iterator;
  typedef std::vector<T>::const_reverse_iterator const_reverse_iterator;
  typedef typename std::vector<T>::reference reference;
  typedef typename std::vector<T>::const_reference const_reference;

  STLVector():vector<T>(){}
  explicit STLVector(size_type n, const T& value = T()){assign(n, value);}
  template <class InputIterator>
  STLVector(InputIterator first, InputIterator last){assign(first, last);}
  explicit STLVector(const vector<T>& x){assign(x.begin(), x.end());}
  STLVector(const STLVector<T>& x){assign(x.begin(), x.end());}

  // iterators
  STLVector<T>& operator=(const vector<T>& x) {
    assign(x.begin(), x.end());
    return *this;
  }
  STLVector<T>& operator=(const STLVector<T>& x) {
    assign(x.begin(), x.end());
    return *this;
  }
  using vector<T>::begin;
  using vector<T>::end;
  using vector<T>::rbegin;
  using vector<T>::rend;
 
  // capacity
  using vector<T>::size;
  using vector<T>::max_size;
  using vector<T>::resize;
  using vector<T>::capacity;
  using vector<T>::clear;
  using vector<T>::empty;
  void reserve(size_type n) {
    try {
      vector<T>::reserve(n);
    } catch(std::bad_alloc) {
      ZLOGF<<"STLVector: Failed to allocate memory!";
    }
  }

  // element access
  reference operator[](size_type i) {return at(i);}
  const_reference operator[](size_type i) const {return at(i);}
  reference at(size_type i) {
    ZRCHECK_LT(i, size());
    return vector<T>::at(i);
  }
  const_reference at(size_type i) const {
    ZRCHECK_LT(i, size());
    return vector<T>::at(i);
  }
  reference front() {
    ZRCHECK_FALSE(empty()) << "Calling front() when vector is empty!";
    return vector<T>::front();
  }
  const_reference front() const {
    ZRCHECK_FALSE(empty()) << "Calling front() when vector is empty!";
    return vector<T>::front();
  }
  reference back() {
    ZRCHECK_FALSE(empty()) << "Calling back() when vector is empty!";
    return vector<T>::back();
  }
  const_reference back() const {
    ZRCHECK_FALSE(empty()) << "Calling back() when vector is empty!";
    return vector<T>::back();
  }

  // modifiers
  template <class InputIterator>
  void assign(InputIterator first, InputIterator last) {
    try {
      vector<T>::assign(first, last);
    } catch(std::bad_alloc) {
      ZLOGF<<"STLVector: Failed to allocate memory!";
    } catch(std::length_error) {
      ZLOGF<<"STLVector: Length of vector is longer than "<<ZVAR(max_size())<<ZVAR(sizeof(T));
    }
  }
  void assign(size_type n, const T& u) {
    try {
      vector<T>::assign(n, u);
    } catch(std::bad_alloc) {
      ZLOGF<<"STLVector: Failed to allocate memory!";
    } catch(std::length_error) {
      ZLOGF<<"STLVector: Length of vector is longer than "<<ZVAR(max_size())<<ZVAR(sizeof(T));
    }
  }
  void push_back(const_reference x) {
    try {
      vector<T>::push_back(x);
    } catch(std::bad_alloc) {
      ZLOGF<<"STLVector: Failed to allocate memory!";
    } catch(std::length_error) {
      ZLOGF<<"STLVector: Length of vector is longer than "<<ZVAR(max_size())<<ZVAR(sizeof(T));
    }
  }
  using vector<T>::pop_back;
  iterator insert (iterator position, const T& x) {
    try {
      return vector<T>::insert(position, x);
    } catch(std::bad_alloc) {
      ZLOGF<<"STLVector: Failed to allocate memory!";
    } catch(std::length_error) {
      ZLOGF<<"STLVector: Length of vector is longer than "<<ZVAR(max_size())<<ZVAR(sizeof(T));
    }
    return end();
  }
  void insert (iterator position, size_type n, const T& x) {
    try {
      return vector<T>::insert(position, n, x);
    } catch(std::bad_alloc) {
      ZLOGF<<"STLVector: Failed to allocate memory!";
    } catch(std::length_error) {
      ZLOGF<<"STLVector: Length of vector is longer than "<<ZVAR(max_size())<<ZVAR(sizeof(T));
    }
    return end();
  }
  template <class InputIterator>
  void insert (iterator position, InputIterator first, InputIterator last) {
    try {
      vector<T>::insert(position, first, last);
    } catch(std::bad_alloc) {
      ZLOGF<<"STLVector: Failed to allocate memory!";
    } catch(std::length_error) {
      ZLOGF<<"STLVector: Length of vector is longer than "<<ZVAR(max_size())<<ZVAR(sizeof(T));
    }
  }
  using vector<T>::erase;
  using vector<T>::swap;

  // Allocator
  using vector<T>::get_allocator;

  // Special
  T* Data() {return &(front());}
  const T* Data() const {return &(front());}
};
#else
template<typename T>
class STLVector : public std::vector<T> {
public:
  typedef std::vector<T>::iterator iterator;
  typedef std::vector<T>::reverse_iterator reverse_iterator;
  typedef std::vector<T>::const_iterator const_iterator;
  typedef std::vector<T>::const_reverse_iterator const_reverse_iterator;
  typedef typename std::vector<T>::reference reference;
  typedef typename std::vector<T>::const_reference const_reference;

  STLVector():vector<T>(){}
  explicit STLVector(size_type n, const T& value = T()):vector<T>(n, value){}
  template <class InputIterator>
  STLVector(InputIterator first, InputIterator last):vector<T>(first, last){}
  explicit STLVector(const vector<T>& x):vector<T>(x){}
  STLVector(const STLVector<T>& x):vector<T>(x){}

  // iterators
  using vector<T>::operator=;
  using vector<T>::begin;
  using vector<T>::end;
  using vector<T>::rbegin;
  using vector<T>::rend;

  // capacity
  using vector<T>::size;
  using vector<T>::max_size;
  using vector<T>::resize;
  using vector<T>::capacity;
  using vector<T>::empty;
  using vector<T>::reserve;

  // element access
  using vector<T>::operator[];
  using vector<T>::at;
  using vector<T>::front;
  using vector<T>::back;

  // modifiers
  using vector<T>::assign;
  using vector<T>::push_back;
  using vector<T>::pop_back;
  using vector<T>::insert;
  using vector<T>::erase;
  using vector<T>::swap;
  using vector<T>::clear;

  // Allocator
  using vector<T>::get_allocator;

  // Special
  T* Data() {return &(front());}
  const T* Data() const {return &(front());}
};
#endif

template<typename T>
class IOObject<STLVector<T*> >
{
public:
  static void WriteFileR(RecordFile &rf, const zint32 label, const STLVector<T*> &src) {
    return WriteFileR1By1(rf, label, src);
  }
  static void ReadFileR(RecordFile &rf, const zint32 label, STLVector<T*> &dst) {
    return ReadFileR1By1(rf, label, dst);
  }

  /// When the object inside STLVector is not SIMPLE_IOOBJECT, it must access 1 by 1,
  /// instead of access as a raw array.
  static const int RF_SIZE = 1;
  static const int RF_DATA = 2;
  static void WriteFileR1By1(RecordFile &rf, const zint32 label, const STLVector<T*> &src) {
    zuint64 len = src.size();
    rf.WriteChildBegin(label);
      IOObj::WriteFileR(rf, RF_SIZE, len);
      rf.WriteRepeatBegin(RF_DATA);
      for (zuint64 i = 0; i < len; ++i) {
        rf.WriteRepeatChild();
        IOObj::WriteFileR(rf, *(src[i]));
      }
      rf.WriteRepeatEnd();
    rf.WriteChildEnd();
  }
  static void ReadFileR1By1(RecordFile &rf, const zint32 label, STLVector<T*> &dst) {
    for (zuint i = 0; i < dst.size(); ++i)
      delete dst[i];
    dst.clear();
    if (!rf.LabelExist(label)) {
      return;
    }
    rf.ReadChildBegin(label);
      zuint64 len;
      IOObj::ReadFileR(rf, RF_SIZE, len);
      if (len != 0) {
        dst.reserve(len);
      }
      rf.ReadRepeatBegin(RF_DATA);
      while(rf.ReadRepeatChild()) {
        T* v = new T;
        IOObj::ReadFileR(rf, *v);
        dst.push_back(v);
      }
      rf.ReadRepeatEnd();
      ZCHECK_EQ(dst.size(), len) << "The length recorded is different from the actual length of data";
    rf.ReadChildEnd();
  }
};
template<typename T>
class IOObject<STLVector<T> >
{
public:
  static void WriteFileB(FILE *fp, const STLVector<T> &src) {
    zuint64 len = src.size();
    IOObj::WriteFileB(fp, len);
    if (len != 0) IOObject<T>::WriteFileB(fp, src.data(), len);
  }
  static void ReadFileB(FILE *fp, STLVector<T> &dst) {
    zuint64 len;
    IOObj::ReadFileB(fp, len);
    if (len != 0) {
      dst.resize(len);
      IOObj::ReadFileB(fp, dst.data(), len);
    } else {
      dst.clear();
    }
  }
  static void WriteFileR(RecordFile &rf, const zint32 label, const STLVector<T> &src) {
    zuint64 len = src.size();
    IOObj::WriteFileR(rf, label, len);
    if (len != 0) IOObj::WriteFileR(rf, src.data(), len);
  }
  static void ReadFileR(RecordFile &rf, const zint32 label, STLVector<T> &dst) {
    if (!rf.LabelExist(label)) {
      dst.clear();
      return;
    }
    zuint64 len;
    IOObj::ReadFileR(rf, label, len);
    if (len != 0) {
      dst.resize(len);
      IOObj::ReadFileR(rf, dst.data(), len);
    } else {
      dst.clear();
    }
  }

  /// When the object inside STLVector is not SIMPLE_IOOBJECT, it must access 1 by 1,
  /// instead of access as a raw array.
  static const int RF_SIZE = 1;
  static const int RF_DATA = 2;
  static void WriteFileR1By1(RecordFile &rf, const zint32 label, const STLVector<T> &src) {
    zuint64 len = src.size();
    rf.WriteChildBegin(label);
      IOObj::WriteFileR(rf, RF_SIZE, len);
      rf.WriteRepeatBegin(RF_DATA);
      for (zuint64 i = 0; i < len; ++i) {
        rf.WriteRepeatChild();
        IOObj::WriteFileR(rf, src[i]);
      }
      rf.WriteRepeatEnd();
    rf.WriteChildEnd();
  }
  static void ReadFileR1By1(RecordFile &rf, const zint32 label, STLVector<T> &dst) {
    dst.clear();
    if (!rf.LabelExist(label)) {
      return;
    }
    rf.ReadChildBegin(label);
      zuint64 len;
      IOObj::ReadFileR(rf, RF_SIZE, len);
      if (len != 0) {
        dst.reserve(len);
      }
      rf.ReadRepeatBegin(RF_DATA);
      while(rf.ReadRepeatChild()) {
        T v;
        IOObj::ReadFileR(rf, v);
        dst.push_back(v);
      }
      rf.ReadRepeatEnd();
      ZCHECK_EQ(dst.size(), len) << "The length recorded is different from the actual length of data";
    rf.ReadChildEnd();
  }
};
}
